package fge;

import com.mxgraph.swing.util.mxSwingConstants;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JPanel;

import com.mxgraph.model.mxCell;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.swing.util.mxGraphTransferable;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import fge.Main.UserSuppliedNoFile;
import java.awt.Cursor;
import java.awt.event.ActionEvent;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;

public class LibraryPalette extends JPanel {

	/**
	 *
	 */
	private static final long serialVersionUID = 7771113885935187066L;
	/**
	 *
	 */
	protected JLabel selectedEntry = null;
	/**
	 *
	 */
	protected mxEventSource eventSource = new mxEventSource(this);
	/**
	 *
	 */
	protected Color gradientColor = new Color(117, 195, 173);
	/**
	 * Indicates if components can be right-clicked to open a pop-up menu and to delete it from this LibaryPalette
	 */
	protected boolean editable = false;
	private Main mm;
	protected String title;
	protected HashMap<JLabel, LibraryTuple> cells;

	/**
	 *
	 */
	@SuppressWarnings("serial")
	public LibraryPalette(Main mm, boolean editable, String title) {
		this.mm = mm;
		this.editable = editable;
		this.title = title;
		this.cells = new HashMap<JLabel, LibraryTuple>();


		setBackground(new Color(149, 230, 190));
		setLayout(new FlowLayout(FlowLayout.LEADING, 5, 5));

		// Clears the current selection when the background is clicked
		addMouseListener(new MouseListener() {

			/*
			 * (non-Javadoc)
			 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
			 */
			public void mousePressed(MouseEvent e) {
				clearSelection();
			}

			/*
			 * (non-Javadoc)
			 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
			 */
			public void mouseClicked(MouseEvent e) {
			}

			/*
			 * (non-Javadoc)
			 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
			 */
			public void mouseEntered(MouseEvent e) {
			}

			/*
			 * (non-Javadoc)
			 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
			 */
			public void mouseExited(MouseEvent e) {
			}

			/*
			 * (non-Javadoc)
			 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
			 */
			public void mouseReleased(MouseEvent e) {
			}
		});


		JPopupMenu pm = new JPopupMenu();

		pm.add(
				new JMenuItem(new AbstractAction("New library") {

			public void actionPerformed(ActionEvent ae) {
				String str = JOptionPane.showInputDialog(null, "Choose the name of the new library", "Create new library", 1);
				if (str == null) {
					return;
				}
				if (str.equalsIgnoreCase("")) {
					return;
				}
//					System.out.println("str = " + str);

				LibraryPalette.this.mm.insertPalette(str, true);
			}
		}));

		pm.add(
				new JMenuItem(new AbstractAction("Open library") {

			public void actionPerformed(ActionEvent ae) {
				String file;
				try {
					file = LibraryPalette.this.mm.OpenDialog();
				} catch (UserSuppliedNoFile ex) {
					return;	// The user didn't supply any file name, so simply don't open anything
				}

				LibraryPalette lp = LibraryPalette.this.mm.insertPalette(new File(file).getName(), true);
				lp.openFile(file);
			}
		}));


		if (this.editable) {
			pm.add(
					new JMenuItem(new AbstractAction("Close library") {

				public void actionPerformed(ActionEvent ae) {
					int idx = LibraryPalette.this.mm.libraryPane.indexOfTab(LibraryPalette.this.title);
					if (idx == -1) {
						return;
					}

					LibraryPalette.this.mm.libraryPane.remove(idx);
					LibraryPalette.this.mm.libraryPane.repaint();

//						System.out.println("Closed");
				}
			}));
		}
		this.setComponentPopupMenu(pm);



//		LibraryPalette does not support draggin onto it. 
//		Shows a nice icon for drag and drop but doesn't import anything
//		setTransferHandler(new TransferHandler() {
//
//			@Override
//			public boolean canImport(JComponent comp, DataFlavor[] flavors) {
//				return true;
//			}
//		});
	}

	/**
	 *
	 */
	public void setGradientColor(Color c) {
		gradientColor = c;
	}

	/**
	 *
	 */
	public Color getGradientColor() {
		return gradientColor;
	}

	/**
	 *
	 */
	@Override
	public void paintComponent(Graphics g) {
		if (gradientColor == null) {
			super.paintComponent(g);
		} else {
			Rectangle rect = getVisibleRect();

			if (g.getClipBounds() != null) {
				rect = rect.intersection(g.getClipBounds());
			}

			Graphics2D g2 = (Graphics2D) g;

			g2.setPaint(new GradientPaint(0, 0, getBackground(), getWidth(), 0, gradientColor));
			g2.fill(rect);
		}
	}

	/**
	 *
	 */
	public void clearSelection() {
		setSelectionEntry(null, null);
	}

	/**
	 *
	 */
	public void setSelectionEntry(JLabel entry, mxGraphTransferable t) {
		JLabel previous = selectedEntry;
		selectedEntry = entry;

		if (previous != null) {
			previous.setBorder(null);
			previous.setOpaque(false);
		}

		if (selectedEntry != null) {
			selectedEntry.setBorder(BorderFactory.createLineBorder(Color.black));
			selectedEntry.setOpaque(true);
		}

		eventSource.fireEvent(new mxEventObject(mxEvent.SELECT, "entry", selectedEntry, "transferable", t, "previous", previous));
	}

	/**
	 *
	 */
	public void setPreferredWidth(int width) {
		int cols = Math.max(1, width / 55);
		setPreferredSize(new Dimension(width,
				(getComponentCount() * 55 / cols) + 30));
		revalidate();
	}

	public void addTemplate(final String name, String icon_rel_path, mxCell cell) {
		mxRectangle bounds = (mxGeometry) cell.getGeometry().clone();
		final mxGraphTransferable t = new mxGraphTransferable(new Object[]{cell}, bounds);

		ImageIcon icon = new ImageIcon(Main.class.getResource(icon_rel_path));

		// Scales the image if it's too large for the library
		if (icon != null) {
			if (icon.getIconWidth() > 32 || icon.getIconHeight() > 32) {
				icon = new ImageIcon(icon.getImage().getScaledInstance(32, 32, 0));
			}
		}

		final JLabel entry = new JLabel(icon);
		entry.setPreferredSize(new Dimension(50, 50));
		entry.setBackground(LibraryPalette.this.getBackground().brighter());
		entry.setFont(new Font(entry.getFont().getFamily(), 0, 10));
		entry.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));

		entry.setVerticalTextPosition(JLabel.BOTTOM);
		entry.setHorizontalTextPosition(JLabel.CENTER);
		entry.setIconTextGap(0);

		entry.setToolTipText(name);
		entry.setText(name);


		if (this.editable) {
			JPopupMenu m = new JPopupMenu();
			m.add(
					new JMenuItem(new AbstractAction("Delete") {

				public void actionPerformed(ActionEvent ae) {
					LibraryPalette.this.remove(entry);
					LibraryPalette.this.repaint();

					LibraryPalette.this.cells.remove(entry);
					LibraryPalette.this.saveFile();
//						System.out.println("Removed " + entry.getText());
				}
			}));
			entry.setComponentPopupMenu(m);
		}





		entry.addMouseListener(new MouseListener() {

			/**
			 * @see java.awt.event.MouseListener#mousePressed(java.awt.event.MouseEvent)
			 */
			public void mousePressed(MouseEvent e) {
//				setSelectionEntry(entry, t);
			}

			/**
			 * @see java.awt.event.MouseListener#mouseClicked(java.awt.event.MouseEvent)
			 */
			public void mouseClicked(MouseEvent e) {
			}

			/**
			 * @see java.awt.event.MouseListener#mouseEntered(java.awt.event.MouseEvent)
			 */
			public void mouseEntered(MouseEvent e) {
				entry.setOpaque(true);
				entry.repaint();
			}

			/**
			 * @see java.awt.event.MouseListener#mouseExited(java.awt.event.MouseEvent)
			 */
			public void mouseExited(MouseEvent e) {
				entry.setOpaque(false);
				entry.repaint();
			}

			/**
			 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
			 */
			public void mouseReleased(MouseEvent e) {
			}
		});

		// Install the handler for dragging nodes into a graph
		DragGestureListener dragGestureListener = new DragGestureListener() {

			public void dragGestureRecognized(DragGestureEvent e) {
//				System.out.println("dragGestureRecognized");
				entry.setOpaque(false);
				entry.repaint();

				e.startDrag(null, mxSwingConstants.EMPTY_IMAGE, new Point(), t, null);
			}
		};

		DragSource dragSource = new DragSource();
		dragSource.createDefaultDragGestureRecognizer(entry, DnDConstants.ACTION_COPY, dragGestureListener);

		add(entry);

		this.cells.remove(entry);
		this.cells.put(entry, new LibraryTuple(icon_rel_path, cell));
//		System.out.println("this.cells.size() = " + this.cells.size());

		saveFile();
	}

	protected void openFile(String file_path) {

		XStream xstream = new XStream(new DomDriver());
		xstream.setMode(XStream.ID_REFERENCES);	// relative or absolute references can grow quite deep (=> large xml); prefer flat ids instead
		Object coll[] = (Object[]) xstream.fromXML(new File(file_path));
		for (Object c : coll) {
			System.out.println("c = " + c);

			if ( !(c instanceof LibraryTuple) ) continue;
			LibraryTuple t = (LibraryTuple) c;

			this.addTemplate(t.cell.toString(), t.icon_rel_path, t.cell);
			
		}
	}

	protected void saveFile() {
		if (!this.editable) {
			return; // non-editable libaries (only the Basic library for now) has no backing file
		}

//		System.out.println("this.cells.size() = " + this.cells.size());


		XStream xstream = new XStream(new DomDriver());
		xstream.setMode(XStream.ID_REFERENCES);	// relative or absolute references can grow quite deep (=> large xml); prefer flat ids instead

		String file = this.title;

		try {
			FileWriter fw = new FileWriter(file);
			BufferedWriter out = new BufferedWriter(fw);
			xstream.toXML(this.cells.values().toArray(), out); // .toArray is important to avoid outer class reference
			out.close();
		} catch (IOException e) {
			JOptionPane.showMessageDialog(null,
					"Could not save to file '" + file + "'.",
					"File I/O error",
					JOptionPane.ERROR_MESSAGE);
		}

		System.out.println("Updated File");
	}

	/**
	 * @param eventName
	 * @param listener
	 * @see com.mxgraph.util.mxEventSource#addListener(java.lang.String, com.mxgraph.util.mxEventSource.mxIEventListener)
	 */
	public void addListener(String eventName, mxIEventListener listener) {
		eventSource.addListener(eventName, listener);
	}

	/**
	 * @return whether or not event are enabled for this palette
	 * @see com.mxgraph.util.mxEventSource#isEventsEnabled()
	 */
	public boolean isEventsEnabled() {
		return eventSource.isEventsEnabled();
	}

	/**
	 * @param listener
	 * @see com.mxgraph.util.mxEventSource#removeListener(com.mxgraph.util.mxEventSource.mxIEventListener)
	 */
	public void removeListener(mxIEventListener listener) {
		eventSource.removeListener(listener);
	}

	/**
	 * @param eventName
	 * @param listener
	 * @see com.mxgraph.util.mxEventSource#removeListener(java.lang.String, com.mxgraph.util.mxEventSource.mxIEventListener)
	 */
	public void removeListener(mxIEventListener listener, String eventName) {
		eventSource.removeListener(listener, eventName);
	}

	/**
	 * @param eventsEnabled
	 * @see com.mxgraph.util.mxEventSource#setEventsEnabled(boolean)
	 */
	public void setEventsEnabled(boolean eventsEnabled) {
		eventSource.setEventsEnabled(eventsEnabled);
	}
}
